package com.ruifu.act.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fhzncloud.cloud.common.core.util.PageR;
import com.fhzncloud.cloud.common.core.util.R;
import com.fhzncloud.cloud.common.core.util.StringUtils;
import com.ruifu.act.dto.ModelDto;
import com.ruifu.act.service.ProcessModelService;
import com.ruifu.act.vo.ProcessDefinitionVO;
import com.ruifu.common.vo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * ClassName: ProcessModelServiceImpl <br/>
 * Description: <br/>
 * date: 2020-5-7-0007 17:13<br/>
 *
 * @author liun<br />
 * @since JDK 1.8
 */
@Service
@Slf4j
public class ProcessModelServiceImpl implements ProcessModelService {

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 查询指定条件下，该账号账套下对应的模型是否存在
     *
     * @param modelName
     * @param modelKey
     * @param tenantId
     * @return
     */
    @Override
    public R<Boolean> verifyModelIsExist(String modelName, String modelKey, String tenantId) {
        long count = 0L;
        if (StringUtils.isNotEmpty(modelName)) {
            count += repositoryService.createModelQuery()
                    .modelName(modelName)
                    .modelTenantId(tenantId)
                    .count();

        } else if (StringUtils.isNotEmpty(modelKey)) {
            count += repositoryService.createModelQuery()
                    .modelKey(modelKey)
                    .modelTenantId(tenantId)
                    .count();
        }

        if (count == 0L) {
            return R.ok(false, "对应查询model不存在");
        } else {
            return R.ok(true, "对应查询model存在");
        }
    }

    /**
     * 根据账套id分页查询流程模板
     *
     * @param tenantId
     * @param pageNum
     * @param sizeNum
     * @return
     */
    @Override
    public PageR<List<Model>> queryModelListByTenantId(String tenantId, Integer pageNum, Integer sizeNum) {
        long totalNum = repositoryService
                .createModelQuery()
                .modelTenantId(tenantId)
                .count();

        List<Model> modelList = repositoryService
                .createModelQuery()
                .modelTenantId(tenantId)
                .orderByLastUpdateTime()
                .desc()
                .listPage((pageNum - 1) * sizeNum, sizeNum);

        return PageR.ok(modelList, totalNum);
    }

    /**
     * 根据账套id分页查询最新版本的流程定义
     *
     * @param tenantId
     * @param pageNum  开始条数
     * @param sizeNum  结束条数
     * @return
     */
    @Override
    public PageR<List<ProcessDefinitionVO>> queryProcessDefinitionByTenantId(String tenantId, Integer pageNum, Integer sizeNum) {
        List<ProcessDefinitionVO> processDefinitionVoList = new ArrayList<>();

        long totalNum = repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionTenantId(tenantId)
                .latestVersion()
                .count();

        List<ProcessDefinition> processDefinitionList = repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionTenantId(tenantId)
                .latestVersion()
                .listPage((pageNum - 1) * sizeNum, sizeNum);

        if (processDefinitionList.size() > 0) {
            for (ProcessDefinition processDefinition : processDefinitionList) {
                ProcessDefinitionVO processDefinitionVO = new ProcessDefinitionVO();
                //部署id
                processDefinitionVO.setGetDeploymentId(processDefinition.getDeploymentId());
                //实例id
                processDefinitionVO.setGetId(processDefinition.getId());
                //流程定义主键(标识)
                processDefinitionVO.setGetKey(processDefinition.getKey());
                //流程定义名称
                processDefinitionVO.setGetName(processDefinition.getName());
                //流程定义版本
                processDefinitionVO.setGetVersion(processDefinition.getVersion());
                //流程定义租户id(账套)
                processDefinitionVO.setGetTenantId(processDefinition.getTenantId());
                processDefinitionVO.setIsSuspended(processDefinition.isSuspended());
                //部署时间
                processDefinitionVO.setDeployTime(repositoryService
                        .createDeploymentQuery()
                        .deploymentId(processDefinition.getDeploymentId())
                        .singleResult()
                        .getDeploymentTime());
                //xml
                processDefinitionVO.setGetResourceName(processDefinition.getResourceName());
                //png
                processDefinitionVO.setGetDiagramResourceName(processDefinition.getDiagramResourceName());

                //添加部署描述
                processDefinitionVO.setProcessDescription(processDefinition.getDescription());

                processDefinitionVoList.add(processDefinitionVO);
            }
        }


        return PageR.ok(processDefinitionVoList, totalNum);
    }

    /**
     * 初始化一个空的流程模板，并返回对应的模板id
     *
     * @param modelDto
     * @return
     */
    @Override
    public String initEmptyModel(ModelDto modelDto, String tenantId) {

        //首先根据key查询是否有同样的key插入，有的话则不处理直接返回
        long count = repositoryService.createModelQuery()
                .modelKey(modelDto.getKey())
                .modelTenantId(tenantId)
                .count();

        if (count == 0L) {
            //初始化一个空模型
            Model model = repositoryService.newModel();
            ObjectNode modelNode = objectMapper.createObjectNode();

            modelNode.put(ModelDataJsonConstants.MODEL_NAME, modelDto.getName());
            modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, modelDto.getDescription());
            modelNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);

            model.setTenantId(tenantId);
            model.setName(modelDto.getName());
            model.setKey(modelDto.getKey());
            model.setMetaInfo(modelNode.toString());
            repositoryService.saveModel(model);

            String id = model.getId();
            //完善ModelEditorSource
            ObjectNode editorNode = objectMapper.createObjectNode();
            editorNode.put("id", "canvas");
            editorNode.put("resourceId", "canvas");
            ObjectNode stencilSetNode = objectMapper.createObjectNode();
            stencilSetNode.put("namespace",
                    "http://b3mn.org/stencilset/bpmn2.0#");
            editorNode.put("stencilset", stencilSetNode);

            repositoryService.addModelEditorSource(id, editorNode.toString().getBytes(StandardCharsets.UTF_8));

            return id;
        }

        return null;
    }

    /**
     * 根据modelid从流程模板中部署发布一个流程定义
     *
     * @param modelId
     * @return
     */
    @Override
    public R deployModel(String modelId) {
        //获取模型
        Model modelData = repositoryService.getModel(modelId);

        if (null == modelData) {
            return R.failed("部署失败，请选择正确的流程模型部署");
        } else {
            byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
            if (bytes == null) {
                return R.failed("模型数据为空，请先设计流程并成功保存，再进行发布。");
            }
            JsonNode modelNode;
            try {
                modelNode = new ObjectMapper().readTree(bytes);
            } catch (IOException e) {
                log.error("ObjectMapper().readTree() 失败", e);
                return R.failed("部署失败");
            }
            BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            if (bpmnModel.getProcesses().size() == 0) {
                return R.failed("数据模型不符要求，请至少设计一条主线流程。");
            }

            byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);

            //部署流程模板(根据InputStream进行部署)
            String processName = modelData.getName() + ".bpmn20.xml";

            Deployment deployment = repositoryService.createDeployment()
                    .name(modelData.getName())
                    .key(modelData.getKey())
                    .tenantId(modelData.getTenantId())
                    .addString(processName, new String(bpmnBytes, StandardCharsets.UTF_8))
                    .deploy();

            modelData.setDeploymentId(deployment.getId());
            repositoryService.saveModel(modelData);

            return R.ok(null, "部署成功");

        }
    }

    /**
     * 将对应流程模板输出。xml格式
     *
     * @param request
     * @param response
     * @param modelId
     */
    @Override
    public void exportModelToXml(HttpServletRequest request, HttpServletResponse response, String modelId) {
        // 清空response
        response.reset();
        // 设置response的Header
        response.setContentType("application/octet-stream");
        String agent = request.getHeader("USER-AGENT");

        try {
            Model modelData = repositoryService.getModel(modelId);

            if (null != modelData) {
                BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
                JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
                BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
                BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
                byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);

                ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
                String filename = modelData.getKey() + ".bpmn20.xml";

                // 火狐
                if (agent != null && agent.toLowerCase().indexOf("firefox") > 0) {
                    String enableFileName = "=?UTF-8?B?" + (new String(Base64.encodeBase64(filename.getBytes(StandardCharsets.UTF_8)))) + "?=";
                    response.setHeader("Content-Disposition", "attachment; filename=" + enableFileName);
                } else {
                    String enableFileName = new String(filename.getBytes("GBK"), StandardCharsets.ISO_8859_1);
                    response.setHeader("Content-Disposition", "attachment; filename=" + enableFileName);
                }
                IOUtils.copy(in, response.getOutputStream());
                response.flushBuffer();
            }

        } catch (Exception e) {
            log.error("导出model的xml文件失败：modelId={}", modelId, e);
        }
    }

    /**
     * 批量删除key值相同的所有版本的流程定义
     *
     * @param processDefinitionKeyList
     * @return
     */
    @Override
    public ResultVO deleteProcessDefinition(List<String> processDefinitionKeyList) {
        try {
            if (CollectionUtil.isNotEmpty(processDefinitionKeyList)) {
                for (String processDefinitionKey : processDefinitionKeyList) {
                    //使用流程定义的key查询所有符合条件数据
                    List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
                            .processDefinitionKey(processDefinitionKey)
                            .list();

                    if (CollectionUtil.isNotEmpty(list)) {
                        for (ProcessDefinition pd : list) {
                            //只删除部署的流程定义。 cascade：false 不会联删除到流程实例、历史流程实例和作业。这里还是要保留历史流程记录的！
                            repositoryService.deleteDeployment(pd.getDeploymentId(), false);
                        }
                    }
                }
            }
            return new ResultVO(true, "流程定义删除成功");
        } catch (Exception e) {
            log.error("流程定义删除失败", e);
            return new ResultVO(false, "流程定义删除失败");
        }
    }

    /**
     * 从默认账套的模板数据中拷贝模板信息到新的账套进行初始化
     *
     * @param sourceTenantId
     * @param targetTenantId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean tenantTemplateCopy(Integer sourceTenantId, Integer targetTenantId) {
        boolean result = true;

        //1.根据模板账套id，查询出模版账套对应的所有model信息
        List<Model> templateModelList = repositoryService.createModelQuery().modelTenantId(sourceTenantId.toString()).list();

        if (CollectionUtil.isNotEmpty(templateModelList)) {
            try {
                JsonNode editorNode;

                for (Model model : templateModelList) {
                    //1.model对象值初始化copy
                    Model modelData = repositoryService.newModel();

                    modelData.setTenantId(targetTenantId.toString());
                    modelData.setKey(model.getKey());
                    modelData.setName(model.getName());
                    modelData.setMetaInfo(model.getMetaInfo());

                    repositoryService.saveModel(modelData);

                    //2.model对应的EditorSource对象值初始化copy（流程xml对应的json）
                    editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(model.getId()));
                    if (null != editorNode) {
                        repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes(StandardCharsets.UTF_8));
                    }

                    //3.model对应的EditorSourceExtra对象值初始化copy（流程xml对应的png）
                    byte[] pngBytes = repositoryService.getModelEditorSourceExtra(model.getId());
                    if (null != pngBytes) {
                        repositoryService.addModelEditorSourceExtra(modelData.getId(), pngBytes);
                    }
                }
            } catch (Exception e) {
                result = false;
                log.error("拷贝进程信息异常。源TenantId:" + sourceTenantId + "目标TenantId:" + targetTenantId, e);
            }
        }

        log.info("拷贝模板进程完毕。源TenantId:{}。目标TenantId:{}。", sourceTenantId, targetTenantId);
        return result;
    }

    /**
     * @param is
     * @param name
     * @param key
     * @param description
     * @return
     * @throws Exception
     */
    public String InportXmlToModel(InputStream is, String name,
                                   String key, String description) throws Exception {
        String modelId;

        XMLInputFactory xif = XMLInputFactory.newInstance();
        //InputStreamReader isr = null;
        XMLStreamReader xtr = null;
        try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) {
            //isr = new InputStreamReader(is, "utf-8");
            xtr = xif.createXMLStreamReader(isr);
            BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
            // 处理异常
            if (bpmnModel.getMainProcess() == null
                    || bpmnModel.getMainProcess().getId() == null) {
                throw new Exception("模板文件可能存在问题，请检查后重试！");
            }

            ObjectNode modelNode = new BpmnJsonConverter().convertToJson(bpmnModel);
            Model modelData = repositoryService.newModel();
            modelData.setKey(key);
            modelData.setName(name);

            ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
            modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, name);
            modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
            modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
            modelData.setMetaInfo(modelObjectNode.toString());

            //校验流程类型key，并自动校正
            //updateProcessKey(modelNode);

            repositoryService.saveModel(modelData);
            repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes(StandardCharsets.UTF_8));

            modelId = modelData.getId();
        } catch (Exception e) {
            log.error("模板文件存在问题，导入失败！", e);
            throw new Exception("模板文件存在问题，操作失败！");
        } finally {
            try {
                if (null != xtr) {
                    xtr.close();
                }
            } catch (XMLStreamException e) {
                e.printStackTrace();
            }
        }
        return modelId;
    }

}
